#include <stdio.h>
#include <driver/gpio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include "esp_freertos_hooks.h"
#include "freertos/semphr.h"
#include "esp_system.h"
#include "driver/spi_master.h"
#include "ui.h"
#include "esp_timer.h"

#define PIN_NUM_DC 4
#define PIN_NUM_RST 5
#define PIN_NUM_CS 10
#define PIN_NUM_MOSI 7
#define PIN_NUM_SCK 6
#define LCD_SPI SPI2_HOST
#define LCD_WIDTH 320
#define LCD_HEIGHT 480

#define LV_TICK_PERIOD_MS 1

static void lv_tick_task(void *arg);
static void guiTask(void *pvParameter);

typedef struct {
    uint8_t cmd;
    uint8_t data[16];
    uint8_t databytes; //No of data in data; bit 7 = delay after set; 0xFF = end of cmds.
} lcd_init_cmd_t;

DRAM_ATTR static const lcd_init_cmd_t st_init_cmds[]={
    {0x11, {0}, 0x80},
    {0x29, {0}, 0x80},
	{0Xf0, {0xc3}, 1},
    {0Xf0, {0x96}, 1},
    {0x36, {0x48}, 1},
	{0X3a, {0x05}, 1},
	{0Xe6, {0x0f, 0xf2, 0x3f, 0x4f, 0x4f, 0x28, 0x0e, 0x00}, 8},
	{0Xc5, {0x2a}, 1},
    {0Xe0, {0xf0, 0x03, 0x0a, 0x11, 0x14, 0x1c, 0x3b, 0x55, 0x4a, 0x0a, 0x13, 0x14, 0x1c, 0x1f}, 14},
	{0Xe1, {0xf0, 0x03, 0x0a, 0x0c, 0x0c, 0x09, 0x36, 0x54, 0x49, 0x0f, 0x1b, 0x18, 0x1b, 0x1f}, 14},
	{0Xf0, {0x3c}, 1},
	{0Xf0, {0x69}, 1},
	{0x29, {0}, 0x80},
    {0, {0}, 0xff}
};

spi_device_handle_t spidev;

void lcd_spi_pre_transfer_callback(spi_transaction_t *t) {
    int dc=(int)t->user;
    gpio_set_level(PIN_NUM_DC, dc);
}

esp_err_t lcd_write_cmd(spi_device_handle_t spidev, uint8_t v) {
    spi_transaction_t t = {
        .length=8,
        .tx_data={v},
        .user=(void*)0,
        .flags=SPI_TRANS_USE_TXDATA
    };
    return spi_device_transmit(spidev, &t);
}

esp_err_t lcd_write_data(spi_device_handle_t spidev, const void *buf, int len) {
    spi_transaction_t t = {
        .length=len*8,
        .tx_buffer=buf,
        .user=(void*)1,
    };
    return spi_device_transmit(spidev, &t);
}

void app_main(void)
{
    xTaskCreate(guiTask, "gui", 4096*5, NULL, 0, NULL);
}

SemaphoreHandle_t xGuiSemaphore;

void lcd_flush_cb(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_map) {

    uint32_t w = ( area->x2 - area->x1 + 1 );
    uint32_t h = ( area->y2 - area->y1 + 1 );

    esp_err_t ret;

    ret = lcd_write_cmd(spidev, 0x2a);
    ESP_ERROR_CHECK(ret);

    static uint8_t x1x2[4];
    x1x2[0] = (area->x1>>8)&0xff;
    x1x2[1] = area->x1&0xff;
    x1x2[2] = (area->x2>>8)&0xff;
    x1x2[3] = area->x2&0xff;
    ret = lcd_write_data(spidev, x1x2, 4);
    ESP_ERROR_CHECK(ret);

    ret = lcd_write_cmd(spidev, 0x2b);
    ESP_ERROR_CHECK(ret);

    static uint8_t y1y2[4];
    y1y2[0] = (area->y1>>8)&0xff;
    y1y2[1] = area->y1&0xff;
    y1y2[2] = (area->y2>>8)&0xff;
    y1y2[3] = area->y2&0xff;
    ret = lcd_write_data(spidev, y1y2, 4);
    ESP_ERROR_CHECK(ret);
printf("ddd %ld %ld %d\n", w, h, color_map[20].full);
    ret = lcd_write_cmd(spidev, 0x2c);
    ESP_ERROR_CHECK(ret);

    for (int i = 0; i < w*h*sizeof(lv_color_t); ++i) {
        color_map[i].full = 0x0;
    }
printf("ddd %ld %ld %d\n", w, h, color_map[20].full);
    ret = lcd_write_data(spidev, color_map, w*h*sizeof(lv_color_t));
    ESP_ERROR_CHECK(ret);

    lv_disp_flush_ready(drv);
}

static void guiTask(void *pvParameter) {

    (void) pvParameter;
    xGuiSemaphore = xSemaphoreCreateMutex();

    lv_init();

    esp_err_t ret;

    // 初始化spi总线
    spi_bus_config_t spiconf = {
        .mosi_io_num=PIN_NUM_MOSI,
        .miso_io_num=-1,
        .sclk_io_num=PIN_NUM_SCK,
        .quadwp_io_num=-1,
        .quadhd_io_num=-1,
        .max_transfer_sz=320*100*8*2
    };
    ret = spi_bus_initialize(LCD_SPI, &spiconf, SPI_DMA_CH_AUTO);
    ESP_ERROR_CHECK(ret);

    // 添加spi设备
    spi_device_interface_config_t devconf = {
        .clock_speed_hz=SPI_MASTER_FREQ_40M,
        .mode=0,
        .spics_io_num=PIN_NUM_CS,
        .queue_size=1,
        .flags=0,
        .pre_cb=lcd_spi_pre_transfer_callback
    };
    ret = spi_bus_add_device(LCD_SPI, &devconf, &spidev);
    ESP_ERROR_CHECK(ret);

    // 初始化gpio
    gpio_set_direction(PIN_NUM_DC, GPIO_MODE_OUTPUT);
    gpio_set_direction(PIN_NUM_RST, GPIO_MODE_OUTPUT);

    // 重启屏幕
    gpio_set_level(PIN_NUM_RST, 0);
    vTaskDelay(100 / portTICK_PERIOD_MS); 
    gpio_set_level(PIN_NUM_RST, 1);
    vTaskDelay(100 / portTICK_PERIOD_MS);

    // 初始化屏幕
    int cmd = 0;
    while (st_init_cmds[cmd].databytes != 0xff) {
        ret = lcd_write_cmd(spidev, st_init_cmds[cmd].cmd);
        ESP_ERROR_CHECK(ret);

        if ((st_init_cmds[cmd].databytes&0x1f) > 0) {
            ret = lcd_write_data(spidev, st_init_cmds[cmd].data, (st_init_cmds[cmd].databytes&0x1f));
            ESP_ERROR_CHECK(ret);
        }

        if (st_init_cmds[cmd].databytes&0x80) {
            vTaskDelay(100 / portTICK_PERIOD_MS);
        }
        cmd++;
    }

    // 申请绘图缓冲
    lv_color_t* buf1 = heap_caps_malloc(LCD_WIDTH*100 * sizeof(lv_color_t), MALLOC_CAP_DMA);
    assert(buf1 != NULL);

    static lv_disp_draw_buf_t disp_buf;
    lv_disp_draw_buf_init(&disp_buf, buf1, NULL, LCD_WIDTH*100);

    // 注册屏幕驱动
    lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&disp_drv);
    disp_drv.hor_res = LCD_WIDTH;
    disp_drv.ver_res = LCD_HEIGHT;
    disp_drv.flush_cb = lcd_flush_cb;
    disp_drv.draw_buf = &disp_buf;
    lv_disp_drv_register(&disp_drv);

    // 创建定时器
    const esp_timer_create_args_t periodic_timer_args = {
        .callback = &lv_tick_task,
        .name = "periodic_gui"
    };
    esp_timer_handle_t periodic_timer;
    ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer));
    ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer, portTICK_PERIOD_MS * 1000));

    lv_obj_t * scr = lv_scr_act();
    lv_obj_t *label1 = lv_label_create(scr);

    ui_init();

    while (1) {
        /* Delay 1 tick (assumes FreeRTOS tick is 10ms */
        vTaskDelay(pdMS_TO_TICKS(10));

        /* Try to take the semaphore, call lvgl related function on success */
        if (pdTRUE == xSemaphoreTake(xGuiSemaphore, portMAX_DELAY)) {
            lv_task_handler();
            xSemaphoreGive(xGuiSemaphore);
       }
    }

    /* A task should NEVER return */
    free(buf1);
    vTaskDelete(NULL);
}

static void lv_tick_task(void *arg) {
    (void) arg;
    lv_tick_inc(LV_TICK_PERIOD_MS);
}